/*
* Copyright (c) 2011 Connection Road.  All rights reserved.
* This program and the accompanying material are made available
* under the terms of the Eclipse Public Licnese v1.0 which is
* available at http://www.eclipse.org/legal/epl-v10.html
*/

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using CrSyncFilesUtils.Enums;
using Microsoft.Synchronization.Files;
using SyncToy;


namespace CrSyncFilesUtils {
    /// <summary>
    /// This has all the information necessary to process one file pair.
    /// 
    /// </summary>
    /// 
    /// 
    [Serializable]
    public partial class FileSyncPairInfo
    {
        /// <summary>
        /// Initializes and instanciate FileSyncPairInfo object with a default unique identifier.
        /// Please use the FileSyncPairManager class to generate a unique pair info.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="uniqueIdentifier">Must be a unique string. Must not contain white spaces or special characters.</param>
        public FileSyncPairInfo(string name,string uniqueIdentifier)
        {
            
            ActiveForRunAll = true;

            FileExcludeAttrArchive = false;
            FileExcludeAttrCompressed = false;
            FileExcludeAttrDirectory = false;
            FileExcludeAttrEncrypted = false;
            FileExcludeAttrHidden = false;
            FileExcludeAttrNoContentIndexed = false;
            FileExcludeAttrOffline = false;
            FileExcludeAttrReadOnly = false;
            FileExcludeAttrReParsePoint = false;
            FileExcludeAttrSparseFile = false;
            FileExcludeAttrSystem = false;
            FileExcludeAttrTemporary = false;

            OptionsCompareFileStreams = false;
            OptionsBackupOlderFiles = true;

            FolderPairActionTypeId = (int)FolderPairActionTypeEnum.Synchronize;
            UsersId = 0;
            FolderPairName = name;
            FileNameIncludes = new List<string>();
            FileNameExludes = new List<string>();
            SubDirectoryExclusionList = new List<string>();

            LastRun = string.Empty;

            // Set file version
            FileVersion = 1.1;

            // Makes this current pair unique to other pairs regardless of names.
            UniqueIdentifier = uniqueIdentifier;

            ExtendedInfo = new CrSyncFilesCustomDictionary();
        }

        public FileSyncPairInfo():this("",Guid.NewGuid().ToString())
        {
            
        }

        public double FileVersion { get; set; }

        public int UsersId { get; set; }

        /// <summary>
        /// Flag holder to determine if the pair should be process in either as "Echo", "Contribute" or full two way sync.
        /// </summary>
        public int FolderPairActionTypeId { get; set; }

        public bool ImportedFromSyncToy { get; set; }
        public string FolderPairName { get; set; }

        [XmlIgnore]
        public ICrSyncLocation LeftFolder { get; set; }

        [XmlIgnore]
        public ICrSyncLocation RightFolder { get; set; }

        
        [XmlElement("LeftFolder")]
        public CrSyncFileLocationSerializer LeftFolderSerializer
        {
            get
            {
                return LeftFolder != null ? new CrSyncFileLocationSerializer(LeftFolder) : null;
            }

            set 
            { 
                LeftFolder = value.GetFileLocation();
            }
        }

        [XmlElement("RightFolder")]
        public CrSyncFileLocationSerializer RightFolderSerializer
        {
            get
            {
                return RightFolder != null ? new CrSyncFileLocationSerializer(RightFolder) : null;
            }

            set
            {
                RightFolder = value.GetFileLocation();
            }
        }
        

        /// <summary>
        /// The DateTime instace converted to string
        /// </summary>
        public string LastRun { get; set; }
        public bool FileExcludeAttrReadOnly { get; set; }
        public bool FileExcludeAttrHidden { get; set; }
        public bool FileExcludeAttrSystem { get; set; }
        public bool FileExcludeAttrDirectory { get; set; }
        public bool FileExcludeAttrArchive { get; set; }
        public bool FileExcludeAttrTemporary { get; set; }
        public bool FileExcludeAttrSparseFile { get; set; }
        public bool FileExcludeAttrReParsePoint { get; set; }
        public bool FileExcludeAttrCompressed { get; set; }
        public bool FileExcludeAttrOffline { get; set; }
        public bool FileExcludeAttrNoContentIndexed { get; set; }
        public bool FileExcludeAttrEncrypted { get; set; }

        /// <summary>
        /// Check file contents using SHA1
        /// If this value is set, the provider will compute a hash value for each file that is based on the contents 
        /// of the whole file stream and use this value to compare files during change detection. 
        /// This option is expensive and will slow synchronization, but provides more robust change detection. 
        /// If this value is not set, an algorithm that compares modification times, file sizes, file names, 
        /// and file attributes will be used to determine whether a file has changed.
        /// </summary>
        public bool OptionsCompareFileStreams { get; set; }

        public bool OptionsExplicitDetectChanges { get; set; }

        /// <summary>
        /// FileSyncOptions.RecycleConflictLoserFiles
        /// If this value is set, the provider will move files that are conflict losers to the recycle bin. 
        /// If this value is not set, the provider will move the files to a specified location. 
        /// Or, if no location is specified, the files will be permanently deleted.
        /// </summary>
        public bool OptionsRecycleConflictLoserFiles { get; set; }

        /// <summary>
        /// FileSyncOptions.RecyclePreviousFileOnUpdates
        /// Save overwritten files in the Recycle Bin
        /// </summary>
        public bool OptionsRecyclePreviousFileOnUpdates { get; set; }

        /// <summary>
        /// FileSyncOptions.RecycleDeletedFiles
        /// If this value is set, the provider will move files deleted during change 
        /// application to the recycle bin. If this value is not set, files will be permanently deleted.
        /// </summary>
        public bool OptionsRecycleDeletedFiles { get; set; }

        public bool BackupOlderFiles { get; set; }

        /// <summary>
        /// Do not set the other ptions directly instead use this meta options for syncToy compatibility.
        /// </summary>
        public bool OptionsBackupOlderFiles
        {
            get { return BackupOlderFiles; }
            set
            {
                BackupOlderFiles = value;
                if(value)
                {
                    OptionsExplicitDetectChanges = true;
                    OptionsRecycleConflictLoserFiles = true;
                    OptionsRecycleDeletedFiles = true;
                    OptionsRecyclePreviousFileOnUpdates = true;
                }
                else
                {
                    OptionsExplicitDetectChanges = true;
                    OptionsRecycleConflictLoserFiles = false;
                    OptionsRecycleDeletedFiles = false;
                    OptionsRecyclePreviousFileOnUpdates = false;
                }
            }
        }

        /// <summary>
        /// Flag to determine wheather this current folder pair is active by default when "Run All" or "Preview All" is invoke.
        /// </summary>
 
        public bool ActiveForRunAll { get; set; }

        /// <summary>
        /// The unique identifier for this current pair
        /// </summary>
        public string UniqueIdentifier { get; set; }

        
        public List<string> FileNameExludes { get; set; }

        public List<string> FileNameIncludes { get; set; }

        /// <summary>
        /// Contains the relative path of the subdirectories for exclusion.
        /// </summary>
        public List<string> SubDirectoryExclusionList { get; set; }
        
        /// <summary>
        /// Retrives the current action type.
        /// </summary>
        public FolderPairActionTypeEnum FolderPairActionType
        {
            get
            {
                switch (FolderPairActionTypeId)
                {
                    case (int) FolderPairActionTypeEnum.Synchronize:
                        return FolderPairActionTypeEnum.Synchronize;
                    case (int) FolderPairActionTypeEnum.Echo:
                        return FolderPairActionTypeEnum.Echo;
                    case (int) FolderPairActionTypeEnum.Contribute:
                        return FolderPairActionTypeEnum.Contribute;
                    default:
                        return FolderPairActionTypeEnum.Synchronize;
                }
            }

            set { FolderPairActionTypeId = (int) value; }
        }


        /// <summary>
        /// Use this variable to add custom or extended info for this current folder pair which do not belong to the standard pair info.
        /// </summary>
        public CrSyncFilesCustomDictionary ExtendedInfo { get; set; }

        
        /// <summary>
        /// Identifies if the given folder name was marked for exclusion or if the given paht is one of the children marked for exclusion.
        /// </summary>
        /// <param name="subFolderPath">Can be a full path or a relative path to the root path</param>
        /// <returns>bool</returns>
        public bool IsSubFolderExcluded(string subFolderPath)
        {
            // Compare using relative path format
            if(SubDirectoryExclusionList.Contains(subFolderPath))
            {
                return true;
            }


            // Incase relative format is not working check using full path.
            var leftChildNode = new FileNode(LeftFolder.Path).Join(subFolderPath);
            var rightChildNode = new FileNode(RightFolder.Path).Join(subFolderPath);

            foreach (var excludedFolder in SubDirectoryExclusionList)
            {
                // Seek left
                var leftParentNode = new FileNode(LeftFolder.Path).Join(excludedFolder);
                if (leftChildNode.IsChildOf(leftParentNode))
                {
                    return true;
                }

                // Seek right
                var rightParentNode = new FileNode(RightFolder.Path).Join(excludedFolder);
                if (rightChildNode.IsChildOf(rightParentNode))
                {
                    return true;
                }
            }
            return false;

        }

        /// <summary>
        /// Gets or sets a list of subfolders to excluded ( separated by comma ).
        /// </summary>
        [XmlIgnore]
        public string SubFoldersToExclude
        {
            get
            {
                return SubDirectoryExclusionList.Count >= 1 ? string.Join(",", SubDirectoryExclusionList.ToArray()) : null;
            }

            set
            {
                var subfolders = value.Split(new[] { ',', ';' });
                SubDirectoryExclusionList = subfolders.ToList();
            }
        }
        
        /// <summary>
        /// Set a single file / folder to be excluded from synchronization.
        /// </summary>
        /// <param name="relativePath"></param>
        public void ExcludeSubfolderFromPath(string relativePath)
        {
            if(!IsSubFolderExcluded(relativePath))
            {
                SubDirectoryExclusionList.Add(relativePath);
            }
        }

        
        public void RemoveSubFolderFromExclusion(string relativePath)
        {
            if(IsSubFolderExcluded(relativePath))
            {
                SubDirectoryExclusionList.Remove(relativePath);
            }
        }

        public string LastRunDisplayString
        {
            get 
            {
                if (string.IsNullOrEmpty(LastRun))
                {
                    return null;
                }
                var dateTime = new DateTime(long.Parse(LastRun));
                return dateTime != DateTime.MinValue ? string.Format("{0:MM/dd/yyy hh:mm:sstt}", dateTime) : null;
            }
        }

        public DateTime DateTimeLastSync
        {
            get
            {
                return string.IsNullOrEmpty(LastRun) ? DateTime.MinValue : new DateTime(long.Parse(LastRun));
            }

            set
            {
                LastRun = value.Ticks.ToString();
            }
        }

        public void AssignNewSourceDrive(string driveName)
        {
            var fileNode = new FileNode(LeftFolder.Path);
            fileNode.UpdateRootPath(driveName);
            LeftFolder.Path = fileNode.Path;
        }

        public void AssignNewDestinationDrive(string driveName)
        {
            var fileNode = new FileNode(RightFolder.Path);
            fileNode.UpdateRootPath(driveName);
            RightFolder.Path = fileNode.Path;
        }


        /// <summary>
        /// Checks if the givent SyncToy folder pair is identical with this current pair using global unique identifier. 
        /// </summary>
        /// <param name="syncEngineConfig"></param>
        /// <returns></returns>
        public bool IsIdenticalWith(SyncEngineConfig syncEngineConfig)
        {
            return (syncEngineConfig.LeftGuid.ToString() == LeftFolder.UniqueIdentifier) && (syncEngineConfig.RightGuid.ToString() == RightFolder.UniqueIdentifier);
        }


        /// <summary>
        /// Checks the left and right location for validation.
        /// </summary>
        /// <returns></returns>
        public CrSyncFileLocationResult Validate(FileSyncPairManager pairManager)
        {
            var leftValidity = Validate(LeftFolder, pairManager);
            var rightValidity = Validate(RightFolder, pairManager);
            
            if ((leftValidity == CrSyncFileLocationResult.Valid) && (rightValidity == CrSyncFileLocationResult.Valid))
            {
                return CrSyncFileLocationResult.Valid;
            }

            if ((leftValidity == CrSyncFileLocationResult.Invalid) && (rightValidity == CrSyncFileLocationResult.Valid))
            {
                return CrSyncFileLocationResult.LeftFolderIsMissing;
            }

            if ((leftValidity == CrSyncFileLocationResult.Valid) && (rightValidity == CrSyncFileLocationResult.Invalid))
            {
                return CrSyncFileLocationResult.RightFolderIsMissing;
            }

            if ((leftValidity == CrSyncFileLocationResult.Invalid) && (rightValidity == CrSyncFileLocationResult.Invalid))
            {
                return CrSyncFileLocationResult.Invalid;
            }

            return CrSyncFileLocationResult.UnIdentified;
        }

        public CrSyncFileLocationResult Validate(ICrSyncLocation location,FileSyncPairManager pairManager)
        {
            switch (location.LocationType)
            {
                case CrSyncFileLocationTypes.Local:
                    var locationLocal = (CrSyncFileLocationLocal) location;
                    return locationLocal.Validate(this, pairManager);
                case CrSyncFileLocationTypes.External:
                    var locationExternal = (CrSyncFileLocationExternal) location;
                    return locationExternal.Validate(this,pairManager);
                case CrSyncFileLocationTypes.Network:
                    var locationNetwork = (CrSyncFileLocationNetwork) location;
                    return locationNetwork.Validate(this, pairManager);
                default:
                    try
                    {
                        var methodInfos = GetType().GetMethods();
                        foreach (var methodInfo in methodInfos)
                        {
                            var hasValidationExtension = methodInfo.Name.Contains("ValidateExtension");
                            if (hasValidationExtension)
                            {
                                var result = methodInfo.Invoke(this, new object[] { location, pairManager });
                                return (CrSyncFileLocationResult)result;
                            }
                        }

                        var locationDefault = (CrSyncFileLocationLocal)location;
                        return locationDefault.Validate(this, pairManager);
                    }
                    catch (MissingMethodException)
                    {
                        var locationDefault = (CrSyncFileLocationLocal)location;
                        return locationDefault.Validate(this, pairManager);
                    }
            }
        }

        public FileSyncOptions GetFileSyncOptions()
        {
            var fileSyncOptions = FileSyncOptions.ExplicitDetectChanges;

            if (OptionsCompareFileStreams)
            {
                fileSyncOptions = fileSyncOptions | FileSyncOptions.ExplicitDetectChanges;
            }

            if (OptionsBackupOlderFiles)
            {
                fileSyncOptions = fileSyncOptions | FileSyncOptions.RecycleConflictLoserFiles | FileSyncOptions.RecycleDeletedFiles | FileSyncOptions.RecyclePreviousFileOnUpdates;
            }

            return fileSyncOptions;
        }

        public FileAttributes GetFileAttributedExclusion()
        {
            var attributeReadonly = (FileExcludeAttrReadOnly) ? FileAttributes.ReadOnly : FileAttributes.Temporary;
            var attributeHidden = (FileExcludeAttrHidden) ? FileAttributes.Hidden : FileAttributes.Temporary;
            var attributeSystem = (FileExcludeAttrSystem) ? FileAttributes.System  :  FileAttributes.Temporary;

            var excludeAttribute = attributeReadonly | attributeHidden | attributeSystem;

            return excludeAttribute;

        }

        public FileSyncScopeFilter GetFileSyncScopeFilter()
        {

            // Exclusion filter
            var filter = new FileSyncScopeFilter();


            const string defaultExclusionPattern = "*.lnk,SyncToy*.dat,*.metadata,CrSycFile*.dat,CRSyncFile*.dat,thumbs.db,desktop.ini";


            foreach (var fileNameExclude in FileNameExludes)
            {
                filter.FileNameExcludes.Add(fileNameExclude);
            }

            // Default filters
            var defaultFilters = defaultExclusionPattern.Split(',');
            foreach (var defaultFilter in defaultFilters)
            {
                filter.FileNameExcludes.Add(defaultFilter);
            }

            // Inclusion filter
            foreach (var fileNameToInclude in FileNameIncludes)
            {
                filter.FileNameIncludes.Add(fileNameToInclude);
            }

            // Attributed filter
            filter.AttributeExcludeMask = GetFileAttributedExclusion();

            return filter;
        }

        public void RemoveMetaData()
        {
            if (File.Exists(LeftFolder.MetaFilePath))
            {
                File.Delete(LeftFolder.MetaFilePath);
            }

            if (File.Exists(RightFolder.MetaFilePath))
            {
                File.Delete(RightFolder.MetaFilePath);
            }
        }

        public void RemoveMarkerFiles()
        {
            if (File.Exists(LeftFolder.MarkerFilePath))
            {
                File.Delete(LeftFolder.MarkerFilePath);
            }

            if (File.Exists(RightFolder.MarkerFilePath))
            {
                File.Delete(RightFolder.MarkerFilePath);
            }
        }

        public void CreateMarkerFiles()
        {
            LeftFolder.CreateMarker();
            RightFolder.CreateMarker();
        }

        public string GetSearchPattern()
        {
            if(FileNameIncludes.Count >= 2)
            {
                return string.Join(";", FileNameIncludes.ToArray());
            }
            
            if(FileNameIncludes.Count > 0)
            {
                return FileNameIncludes[0];
            }

            return "*";
        }

        public void SetSearchPattern(string pattern)
        {
            if(!string.IsNullOrEmpty(pattern))
            {
                if ((pattern.Contains(";")) || (pattern.Contains(",")))
                {
                    var searchPattern = pattern.Split(new[] { ',', ';' });
                    FileNameIncludes = searchPattern.ToList();
                }
                else
                {
                    FileNameIncludes = new List<string> { pattern };
                }
            }
            else
            {
                FileNameIncludes = new List<string>{"*"};
            }
        }

        public string GetExcludePattern()
        {
            if (FileNameExludes.Count >= 2)
            {
                return string.Join(";", FileNameExludes.ToArray());
            }

            if (FileNameExludes.Count > 0)
            {
                return FileNameExludes[0];
            }

            return null;
        }

        public void SetExcludePattern(string pattern)
        {
            if(!string.IsNullOrEmpty(pattern))
            {
                if ((pattern.Contains(";")) || (pattern.Contains(",")))
                {
                    var searchPattern = pattern.Split(new[] { ',', ';' });
                    FileNameExludes = searchPattern.ToList();
                }
                else
                {
                    FileNameExludes = new List<string> { pattern };
                }
            }
            else
            {
                FileNameExludes = new List<string>();
            }
        }
    }

}
